#include "gui/Surface.h"

namespace gui {

Surface::Surface(Surface* surface) {
	init(surface->getSurface(), surface->portion);
}
Surface::Surface(SDL_Surface* surface, Portion portion) {
	init(surface, portion);
}
void Surface::init(SDL_Surface* surface, Portion portion) {
	this->surface = this->createSurface(surface);
	SDL_BlitSurface(surface, NULL, this->surface, NULL);
	this->handleKeyColor(surface, this->surface);
	this->portion.x = portion.x;
	this->portion.y = portion.y;
	this->portion.w = surface->w;
	this->portion.h = surface->h;
}
Surface::Surface(Portion portion, int bpp, RGBColor color, int aMask) {
	this->surface = this->createSurface(portion.w, portion.h, bpp, color, aMask);
	this->portion = portion;
}
Surface::~Surface() {
	SDL_FreeSurface(this->surface);
}

int Surface::getWidth() { return this->surface->w; }
int Surface::getHeight() { return this->surface->h; }
SDL_Surface* Surface::getSurface() { return this->surface; }

void Surface::clear(){
	this->exchangeSurface(this->createSurface(this->surface));
}
void Surface::resize(double percentage){
	this->resize(percentage, percentage);
}
void Surface::resize(int width, int height) {
	if (width == this->surface->w && height == this->surface->h) return;
	this->resize((double)width/(double)this->surface->w, (double)height/(double)this->surface->h);
}
void Surface::resize(double hScale, double vScale){
	this->resizeSurface(hScale, vScale);
}
void Surface::rotate(double angle){
	this->rotateSurface(angle);
}
void Surface::applySurface(Surface* surface) {
	Portion portion;
	portion.x = surface->getLeftmost() - this->getLeftmost();
	portion.y = surface->getTopmost() - this->getTopmost();
	portion.w = surface->getWidth();
	portion.h = surface->getHeight();
	if (portion.x < 0) { portion.w = portion.w - portion.x; }
	if (portion.y < 0) { portion.h = portion.h - portion.y; }
	this->applySurface(surface, portion);
}
void Surface::applySurface(Surface* surface, Portion portion) {
	SDL_Rect rect = this->portionToRect(portion);
	SDL_Rect rect2 = {0,0,rect.w, rect.h};
	SDL_BlitSurface(surface->getSurface(), &rect2, this->surface, &rect);
}
void Surface::setTransparentColor(RGBColor color) {
	SDL_SetColorKey(this->surface, SDL_RLEACCEL | SDL_SRCCOLORKEY, SDL_MapRGB(this->surface->format, color.red, color.green, color.blue));
}
Surface* Surface::crop(Portion portion) {
	//TODO Cual deberia ser el mapPoint de la superficie croppeada?
	RGBColor color = { this->surface->format->Rmask, this->surface->format->Gmask, this->surface->format->Bmask };
	Portion center = { portion.x + portion.w / 2, portion.y + portion.h / 2, portion.w, portion.h };
	Surface* croppedSurface = new Surface(center, this->surface->format->BitsPerPixel, color, this->surface->format->Amask);
	this->fillSurface(croppedSurface->surface, this->surface->format->colorkey);
	this->handleKeyColor(this->surface, croppedSurface->surface);
	croppedSurface->applySurface(this);
	return croppedSurface;
}


//============ Private methods below ===============

SDL_Surface* Surface::createSurface(SDL_Surface* surface) {
	return this->createSurface(surface, surface->w, surface->h);
}
SDL_Surface* Surface::createSurface(SDL_Surface* surface, int w, int h) {
	RGBColor color = { surface->format->Rmask, surface->format->Gmask, surface->format->Bmask };
	return this->createSurface(w, h, surface->format->BitsPerPixel, color, surface->format->Amask);
}
SDL_Surface* Surface::createSurface(int w, int h, int bpp, RGBColor color, int a) {
	return SDL_CreateRGBSurface(SDL_HWSURFACE | SDL_SRCCOLORKEY | SDL_RLEACCEL, w, h, bpp, color.red, color.green, color.blue, a);
}
void Surface::destroySurface(SDL_Surface* surface) {
	SDL_FreeSurface(surface);
}
void Surface::lockSurface(SDL_Surface* surface) {
	if (SDL_MUSTLOCK(surface)) { SDL_LockSurface(surface); }
}
void Surface::unlockSurface(SDL_Surface* surface) {
	if (SDL_MUSTLOCK(surface)) { SDL_UnlockSurface(surface); }
}
void Surface::exchangeSurface(SDL_Surface* surface){
	this->destroySurface(this->surface);
	this->surface = surface;
	this->portion.w = surface->w;
	this->portion.h = surface->h;
}

Uint32 Surface::getPixel(SDL_Surface* surface, int x, int y) {
	Uint32* pixels = (Uint32*) surface->pixels;
	return pixels[(surface->w * y) + x];
}
void Surface::putPixel(SDL_Surface* surface, int x, int y, Uint32 pixel) {
	Uint32* pixels = (Uint32*) surface->pixels;
	pixels[(surface->w * y) + x] = pixel;
}
void Surface::handleKeyColor(SDL_Surface* originalSurface, SDL_Surface* newSurface) {
	if (originalSurface->flags & SDL_SRCCOLORKEY) {
		SDL_SetColorKey(newSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY, originalSurface->format->colorkey);
	}
}
void Surface::setCenter(int x, int y) {
	this->portion.x = x;
	this->portion.y = y;
}

void Surface::fillSurface(RGBColor color) {
	this->fillSurface(this->surface, SDL_MapRGB(this->surface->format, color.red, color.green, color.blue));
}
void Surface::fillSurface(SDL_Surface* surface, Uint32 color) {
	SDL_Rect rect;
	rect.x = 0;
	rect.y = 0;
	rect.h = surface->h;
	rect.w = surface->w;
	SDL_FillRect(surface, &rect, color);
}

void Surface::resizeSurface(double hScale, double vScale){
	if (hScale <= 0 || vScale <= 0) return;
	if (util::doubleEquals(hScale, 1) && util::doubleEquals(vScale, 1)) return;

	int newWidth = util::doubleToInt(this->surface->w * hScale); if (newWidth<1) newWidth=1;
	int newHeight = util::doubleToInt(this->surface->h * vScale); if (newHeight<1) newHeight=1;

	SDL_Surface* stretchedSurface = this->createSurface(this->surface, newWidth, newHeight);

	int hScale_int = (int)(newWidth /(double)this->surface->w);
	int vScale_int = (int)(newHeight/(double)this->surface->h);

	double remX = hScale-hScale_int, remY = vScale-vScale_int;	// rem : remainder (resto)
	double accX = 0.0, accY = 0.0;			// acc : accumulated (acumulado)
	int extraByteX=0, extraByteY=0;			// hack, we need an extra byte

	this->lockSurface(this->surface);
	int i_t = 0;
	for (int i = 0; i < this->surface->w; i++) {
		accX += remX; extraByteX = accX >= 1.0 ? 1 : 0;
		accX -= extraByteX;
		for(int k = 0; k < (hScale_int + extraByteX); k++) {
			int j_t = 0;
			accY = 0.0;
			for (int j = 0; j < this->surface->h; j++) {
				Uint32 pixel = this->getPixel(this->surface, i, j);
				accY += remY; extraByteY = (accY >= 1.0) ? 1 : 0;
				accY -= extraByteY;
				for (int l = 0; l < (vScale_int + extraByteY); l++) {
					this->putPixel(stretchedSurface, i_t + k, j_t + l, pixel);
				}
				j_t+= vScale_int + extraByteY;
			}
		}
		i_t+= hScale_int + extraByteX;
	}
	this->unlockSurface(this->surface);
	this->handleKeyColor(this->surface, stretchedSurface);

	this->exchangeSurface(stretchedSurface);
}

void Surface::rotateSurface(double angle){
	SDL_Surface* rotatedSurface;
	SDL_Surface* aux;

	angle = util::doubleLess(angle, 0, false) ? 2 * PI + angle : angle;
	if ((util::doubleEquals(angle,0)) || (util::doubleEquals(angle, 2*PI))) {
		return;
	}

	if (util::doubleIsBetween(angle, 0, PI / 2.0, false)) {
		rotatedSurface = this->rotateSmallAngle(this->surface, angle);
	} else if (util::doubleEquals(angle, PI / 2.0)) {
		rotatedSurface = this->rotate90(this->surface, true);
	} else if (util::doubleIsBetween(angle, PI / 2.0, PI, false)) {
		aux = this->rotate90(this->surface, true);
		rotatedSurface = this->rotateSmallAngle(aux, angle - PI / 2.0);
		this->destroySurface(aux);
	} else if (util::doubleEquals(angle, PI)) {
		rotatedSurface = this->rotate180(this->surface);
	} else if (util::doubleIsBetween(angle, PI, 3 * PI / 2.0, false)) {
		aux = this->rotate180(this->surface);
		rotatedSurface = this->rotateSmallAngle(aux, angle - PI);
		this->destroySurface(aux);
	} else if (util::doubleEquals(angle, 3 * PI / 2.0)) {
		rotatedSurface = this->rotate90(this->surface, false);
	} else if (util::doubleIsBetween(angle, 3 * PI / 2.0, 2 * PI, false)) {
		aux  = this->rotate90(this->surface, false);
		rotatedSurface = this->rotateSmallAngle(aux, angle - 3 * PI / 2.0);
		this->destroySurface(aux);
	} else {
		this->rotateSurface(angle - 2 * PI);
		return;
	}

	this->exchangeSurface(rotatedSurface);
}

SDL_Surface* Surface::rotate180(SDL_Surface* surface) {
	SDL_Surface* rotatedSurface = this->createSurface(surface);
	this->lockSurface(surface);
	for (int i = 0, ri = surface->w - 1; i < surface->w; i++, ri--) {
		for (int j = 0, rj = surface->h - 1; j < surface->h; j++, rj--) {
			Uint32 pixel = this->getPixel(surface, i, j);
			this->putPixel(rotatedSurface, ri, rj, pixel);
		}
	}
	this->unlockSurface(surface);
	this->handleKeyColor(surface, rotatedSurface);
	return rotatedSurface;
}

SDL_Surface* Surface::rotate90(SDL_Surface* surface, bool clockwise) {
	SDL_Surface* rotatedSurface = this->createSurface(surface, surface->h, surface->w);
	this->lockSurface(surface);
	for (int i = 0, ri = surface->w - 1; i < surface->w; i++, ri--) {
		for (int j = 0, rj = surface->h - 1; j < surface->h; j++, rj--) {
			Uint32 pixel = this->getPixel(surface, i, j);
			if (clockwise) {
				this->putPixel(rotatedSurface, rj, i, pixel);
			} else {
				this->putPixel(rotatedSurface, j, ri, pixel);
			}
		}
	}
	this->unlockSurface(surface);
	this->handleKeyColor(surface, rotatedSurface);
	return rotatedSurface;
}

SDL_Surface* Surface::rotateSmallAngle(SDL_Surface* surface, double angle) {
	double cosAngle = cos(angle), sinAngle = sin(angle);
	int newH = util::doubleToInt(cosAngle * surface->h + sinAngle * surface->w) + 1;
	int newW = util::doubleToInt(cosAngle * surface->w + sinAngle * surface->h) + 1;
	SDL_Surface* rotatedSurface = this->createSurface(surface, newW, newH);

	// Color de transparencia 0000FF.
	Uint32 transparentColor = (surface->flags & SDL_SRCCOLORKEY)
				? surface->format->colorkey
				: SDL_MapRGB(rotatedSurface->format, 0, 0, 255);
	this->fillSurface(rotatedSurface, transparentColor);
	this->lockSurface(surface);
	int dispX = util::doubleToInt(surface->h * sinAngle);
	int a, b; Uint32 pixel;
	for (int i = 0; i < surface->w; i++) {
		for (int j = 0; j < surface->h; j++) {
			pixel = this->getPixel(surface, i, j);
			a = util::doubleToInt((i * cosAngle) - (j * sinAngle)) + dispX;
			b = util::doubleToInt((i * sinAngle) + (j * cosAngle));
			this->putPixel(rotatedSurface, a, b, pixel);
			if (a > 0 && a < rotatedSurface->w) {
				this->putPixel(rotatedSurface, a + 1, b, pixel); // Workaround para huecos al rotar.
			}
		}
	}
	this->unlockSurface(surface);
	this->handleKeyColor(surface, rotatedSurface);
	return rotatedSurface;

}

Portion Surface::rectToPortion(SDL_Rect rect) {
	Portion portion;
	portion.x = rect.x;
	portion.y = rect.y;
	portion.h = rect.h;
	portion.w = rect.w;
	return portion;
}
SDL_Rect Surface::portionToRect(Portion portion) {
    SDL_Rect rect;
    rect.x = portion.x;
    rect.y = portion.y;
    rect.h = portion.h;
    rect.w = portion.w;
    return rect;
}

}
